package com.ccod.mapper.mapper.core;

import com.ccod.mapper.mapper.core.chain.CoreServiceChain;
import com.ccod.mapper.mapper.core.model.MapperRefreshContext;
import com.ccod.mapper.mapper.core.utils.RefreshUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @author: ccod
 * @data: 2020/12/5 11:54
 **/
public class MyMapperRefresh {

    private static final Log log = LogFactory.getLog(MyMapperRefresh.class);

    private SqlSessionFactory sqlSessionFactory;

    private Configuration configuration;

    private File[] mapperFiles;

    private Map<String, Long> fileLastModifiedMap;

    private CoreServiceChain<MapperRefreshContext> coreServiceChain;

    /**
     * 是否开启刷新mapper
     */
    private boolean enabled;

    /**
     * 延迟加载时间
     */
    private int delaySeconds = 10;

    /**
     * 刷新间隔时间
     */
    private int sleepSeconds = 20;

    /**
     * 刷新线程池
     */
    private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);

    public MyMapperRefresh(SqlSessionFactory sqlSessionFactory,
                           File[] mapperFiles,
                           CoreServiceChain<MapperRefreshContext> coreServiceChain,boolean enabled) {
        this.enabled = enabled;
        RefreshUtils.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
        RefreshUtils.notNull(coreServiceChain, "Property 'coreServiceChain' is required");
        this.sqlSessionFactory = sqlSessionFactory;
        this.configuration = sqlSessionFactory.getConfiguration();
        RefreshUtils.notNull(configuration, "Property 'configuration' is required");
        fileLastModifiedMap = Maps.newHashMap();
        this.coreServiceChain = coreServiceChain;
        this.mapperFiles = mapperFiles;
        init();
    }

    /**
     * 功能描述: <br>
     * 〈执行自动刷新〉
     *
     * @Param: []
     * @Return: void
     * @Author: Ccod
     * @Date: 2020/12/5 12:19
     */
    public void run() {
        if (!enabled) {
            return;
        }
        if(this.delaySeconds < 10){
            this.delaySeconds = 10;
            log.warn("建议延迟启动时间不小于10秒,已强制更改mapper refresh 10秒后启动");
        }
        if(this.sleepSeconds < 10){
            this.sleepSeconds = 10;
            log.warn("建议刷新间隔不小于10秒,已强制更改mapper刷新间隔为10秒");
        }
        if (log.isDebugEnabled()) {
            log.debug("mapper自动刷新功能将在" + delaySeconds + "秒后启动，刷新间隔为" + sleepSeconds + "秒");
        }
        executorService.scheduleAtFixedRate(
                () -> this.doRefresh(), delaySeconds, sleepSeconds, TimeUnit.SECONDS
        );
    }

    public void shutdown(){
        this.executorService.shutdown();
    }

    public void doRefresh(){
        synchronized (this){
            List<String> resourceNameList = null;
            if (log.isDebugEnabled()) {
                resourceNameList = Lists.newArrayList();
            }
            MapperRefreshContext context = new MapperRefreshContext();
            context.setConfiguration(configuration);
            long start = System.currentTimeMillis();
            for (File mapperFile : mapperFiles) {
                if (mapperFile == null || !mapperFile.exists() || mapperFile.isDirectory()) {
                    continue;
                }
                if (!fileLastModifiedMap.containsKey(mapperFile.toString())) {
                    continue;
                }
                long resourceModified = 0;
                resourceModified = mapperFile.lastModified();
                Long lastModified = fileLastModifiedMap.get(mapperFile.toString());
                if (lastModified >= resourceModified) {
                    continue;
                }
                context.setMapperFile(mapperFile);
                coreServiceChain.execute(context);
                fileLastModifiedMap.put(mapperFile.toString(),resourceModified);
                if (log.isDebugEnabled()) {
                    resourceNameList.add(mapperFile.toString());
                }
            }
            if (log.isDebugEnabled()) {
                long end = System.currentTimeMillis();
                log.debug(String.format("mapper刷新完成,用时%d毫秒，本次刷新文件列表为:%s", end - start, resourceNameList));
            }
        }
    }

    /**
     * 功能描述: <br>
     * 〈初始化需要热刷新的资源文件〉
     *
     * @Param: []
     * @Return: void
     * @Author: Ccod
     * @Date: 2020/12/5 12:13
     */
    private void init() {
        if (mapperFiles == null || mapperFiles.length < 1) {
            return;
        }
        for (File mapperFile : mapperFiles) {
            try {
                if (mapperFile == null) {
                    continue;
                }
                if (RefreshUtils.isJarFileURL(mapperFile.toURL())) {
                    continue;
                }
                if (mapperFile.exists() && mapperFile.isFile()) {
                    fileLastModifiedMap.put(mapperFile.toString(), mapperFile.lastModified());
                }
            } catch (IOException ex) {
                log.error("resource:[" + mapperFile + "]异常", ex);
            }
        }
    }




    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }

    public void setMapperFiles(File[] mapperFiles) {
        this.mapperFiles = mapperFiles;
    }

    public SqlSessionFactory getSqlSessionFactory() {
        return sqlSessionFactory;
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    public File[] getMapperFiles() {
        return mapperFiles;
    }

    public Map<String, Long> getFileLastModifiedMap() {
        return fileLastModifiedMap;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public int getDelaySeconds() {
        return delaySeconds;
    }

    public void setDelaySeconds(int delaySeconds) {
        this.delaySeconds = delaySeconds;
    }

    public int getSleepSeconds() {
        return sleepSeconds;
    }

    public void setSleepSeconds(int sleepSeconds) {
        this.sleepSeconds = sleepSeconds;
    }
}
